# OneToOneMetadataResolver.java

    package org.codefilarete.stalactite.engine.configurer.dslresolver;

    import java.util.Set;

    import org.codefilarete.reflection.AccessorByMethodReference;
    import org.codefilarete.reflection.AccessorDefinition;
    import org.codefilarete.reflection.Accessors;
    import org.codefilarete.reflection.Mutator;
    import org.codefilarete.reflection.ReadWritePropertyAccessPoint;
    import org.codefilarete.stalactite.dsl.entity.EntityMappingConfiguration;
    import org.codefilarete.stalactite.dsl.naming.ForeignKeyNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.JoinColumnNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.UniqueConstraintNamingStrategy;
    import org.codefilarete.stalactite.engine.configurer.NamingConfiguration;
    import org.codefilarete.stalactite.engine.configurer.ValueAccessPointVariantSupport;
    import org.codefilarete.stalactite.engine.configurer.dslresolver.InheritanceConfigurationResolver.ResolvedConfiguration;
    import org.codefilarete.stalactite.engine.configurer.dslresolver.MetadataSolvingCache.EntitySource;
    import org.codefilarete.stalactite.engine.configurer.model.DirectRelationJoin;
    import org.codefilarete.stalactite.engine.configurer.model.Entity;
    import org.codefilarete.stalactite.engine.configurer.model.ResolvedOneToOneRelation;
    import org.codefilarete.stalactite.engine.configurer.onetoone.OneToOneRelation;
    import org.codefilarete.stalactite.sql.ConnectionConfiguration;
    import org.codefilarete.stalactite.sql.Dialect;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.ForeignKey;
    import org.codefilarete.stalactite.sql.ddl.structure.Key;
    import org.codefilarete.stalactite.sql.ddl.structure.Key.KeyBuilder;
    import org.codefilarete.stalactite.sql.ddl.structure.PrimaryKey;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.stalactite.sql.result.BeanRelationFixer;
    import org.codefilarete.tool.Nullable;
    import org.codefilarete.tool.collection.KeepOrderSet;

    import static org.codefilarete.tool.Nullable.nullable;
    import static org.codefilarete.tool.collection.Iterables.first;

    public class OneToOneMetadataResolver {

    	private final Dialect dialect;
    	private final ConnectionConfiguration connectionConfiguration;

    	public OneToOneMetadataResolver(Dialect dialect, ConnectionConfiguration connectionConfiguration) {
    		this.dialect = dialect;
    		this.connectionConfiguration = connectionConfiguration;
    	}

    	<C, I> Set<EntitySource<?, ?>> resolve(EntitySource<C, I> source) {
    		KeepOrderSet<EntitySource<?, ?>> targetEntities = new KeepOrderSet<>();
    		// configuring one-to-ones owned by this entity
    		source.getResolvedConfigurations().forEach(resolvedConfiguration -> {
    			targetEntities.addAll(resolve(source.getEntity(), resolvedConfiguration.getMappingConfiguration()));
    		});
    		return targetEntities;
    	}

    	private <C, I> Set<EntitySource<?, ?>> resolve(Entity<C, I, ?> entity, EntityMappingConfiguration<C, I> mappingConfiguration) {
    		KeepOrderSet<EntitySource<?, ?>> targetEntities = new KeepOrderSet<>();
    		mappingConfiguration.getOneToOnes().forEach(oneToOne -> {
    			EntitySource<Object, Object> resolve = this.resolve(entity, oneToOne);
    			targetEntities.add(resolve);
    		});
    		// treating relations embedded in insets
    		mappingConfiguration.getPropertiesMapping().getInsets().forEach(inset -> {
    			inset.getConfigurationProvider().getConfiguration().getOneToOnes().forEach(oneToOne -> {
    				EntitySource<Object, Object> resolve = this.resolve(entity, oneToOne.embedInto(inset.getAccessor()));
    				targetEntities.add(resolve);
    			});
    		});
    		return targetEntities;
    	}

    	<SRC, TRGT, SRCID, TRGTID, SRCTABLE extends Table<SRCTABLE>, TRGTTABLE extends Table<TRGTTABLE>>
    	EntitySource<TRGT, TRGTID> resolve(Entity<SRC, SRCID, SRCTABLE> source, OneToOneRelation<SRC, TRGT, TRGTID> oneToOne) {

    		EntitySource<TRGT, TRGTID> targetEntitySource = buildTargetEntity(oneToOne);
    		NamingConfiguration namingConfiguration = first(targetEntitySource.getResolvedConfigurations()).getNamingConfiguration();

    		ReadWritePropertyAccessPoint<TRGT, SRC> reverseAccessPoint = Nullable.nullable(oneToOne.getReverseAccessor()).map(ValueAccessPointVariantSupport::getAccessor).get();
    		DirectRelationJoin<SRCTABLE, TRGTTABLE, ?> tablesJoin = null;
    		BeanRelationFixer<SRC, TRGT> relationFixer;
    		Entity<TRGT, TRGTID, TRGTTABLE> targetEntity = targetEntitySource.getEntity();
    		if (oneToOne.isRelationOwnedByTarget()) {
    			// target owns the relation
    			// we don't create foreign key for table-per-class because source columns should reference different tables (the on per entity) which databases do not allow
    			boolean canCreateForeignKey = !source.isTablePerClass();
    			if (canCreateForeignKey) {
    				if (!targetEntity.isTablePerClass()) {
    					OneToOneOwnedByTargetHelper<SRC, TRGT, SRCID, TRGTID, SRCTABLE, TRGTTABLE> helper = new OneToOneOwnedByTargetHelper<>();
    					ForeignKey<TRGTTABLE, SRCTABLE, SRCID> foreignKey = helper.determineForeignKeyColumns(oneToOne, source.getTable().getPrimaryKey(), targetEntity.getTable(), namingConfiguration.getJoinColumnNamingStrategy(), namingConfiguration.getForeignKeyNamingStrategy());
    					tablesJoin = new DirectRelationJoin<>(foreignKey.getReferencedKey(), foreignKey);

    					// eventually adding unique constraint
    					if (oneToOne.isUnique() && foreignKey.getColumns().size() == 1) {
    						helper.addUniqueConstraint(foreignKey, namingConfiguration.getUniqueConstraintNamingStrategy(), oneToOne.getReverseAccessor().getAccessor());
    					}
    				}
    			} // else: creating foreign key is not possible, nothing special to do

    			relationFixer = determineRelationFixer(oneToOne);
    		} else {
    			// source owns the relation
    			OneToOneOwnedBySourceHelper<SRC, TRGT, SRCID, TRGTID, SRCTABLE, TRGTTABLE> helper = new OneToOneOwnedBySourceHelper<>();
    			ForeignKey<SRCTABLE, TRGTTABLE, TRGTID> foreignKey = helper.determineForeignKeyColumns(oneToOne, source.getTable(), targetEntity.getTable().getPrimaryKey(), namingConfiguration.getJoinColumnNamingStrategy(), namingConfiguration.getForeignKeyNamingStrategy());
    			tablesJoin = new DirectRelationJoin<>(foreignKey);

    			// eventually adding unique constraint
    			if (oneToOne.isUnique() && foreignKey.getColumns().size() == 1) {
    				helper.addUniqueConstraint(foreignKey, namingConfiguration.getUniqueConstraintNamingStrategy(), oneToOne.getTargetProvider());
    			}

    			relationFixer = BeanRelationFixer.of(oneToOne.getTargetProvider());
    		}

    		ResolvedOneToOneRelation<SRC, TRGT, SRCTABLE, TRGTTABLE, ?> entitiesLink = new ResolvedOneToOneRelation<>(
    				targetEntity,
    				oneToOne.getTargetProvider(),
    				reverseAccessPoint,
    				oneToOne.getRelationMode(),
    				oneToOne.isFetchSeparately(),
    				tablesJoin,
    				relationFixer,
    				oneToOne.isRelationOwnedByTarget(),
    				!oneToOne.isNullable()
    		);
    		source.addRelation(entitiesLink);

    		return targetEntitySource;
    	}

    	private <SRC, TRGT, TRGTID> EntitySource<TRGT, TRGTID> buildTargetEntity(OneToOneRelation<SRC, TRGT, TRGTID> oneToOne) {
    		InheritanceConfigurationResolver<TRGT, TRGTID> inheritanceConfigurationResolver = new InheritanceConfigurationResolver<>();
    		KeepOrderSet<ResolvedConfiguration<?, TRGTID>> ancestorsConfigurations = inheritanceConfigurationResolver.resolveConfigurations(oneToOne.getTargetMappingConfiguration());

    		InheritanceMetadataResolver<TRGT, TRGTID, ?> keyMappingApplier = new InheritanceMetadataResolver<>(dialect, connectionConfiguration);
    		return keyMappingApplier.resolve(ancestorsConfigurations);
    	}

    	<SRC, TRGT> BeanRelationFixer<SRC, TRGT> determineRelationFixer(OneToOneRelation<SRC, TRGT, ?> oneToOneRelation) {
    		Mutator<SRC, TRGT> sourceIntoTargetFixer = oneToOneRelation.getTargetProvider();
    		BeanRelationFixer<SRC, TRGT> result;

    		if (oneToOneRelation.getReverseGetter() != null) {
    			AccessorByMethodReference<TRGT, SRC> localReverseGetter = Accessors.accessorByMethodReference(oneToOneRelation.getReverseGetter());
    			AccessorDefinition accessorDefinition = AccessorDefinition.giveDefinition(localReverseGetter);

    			// we take advantage of foreign key computing and presence of AccessorDefinition to build relation fixer which is needed lately in determineRelationFixer(..)
    			Mutator<TRGT, SRC> targetIntoSourceFixer = Accessors.mutatorByMethod(accessorDefinition.getDeclaringClass(), accessorDefinition.getName());
    			result = (src, target) -> {
    				// fixing source on target
    				if (target != null) {    // prevent NullPointerException, actually means no linked entity (null relation), so nothing to do
    					targetIntoSourceFixer.set(target, src);
    				}
    				// fixing target on source
    				sourceIntoTargetFixer.set(src, target);
    			};
    		} else if (oneToOneRelation.getReverseSetter() != null) {
    			// we take advantage of foreign key computing and presence of AccessorDefinition to build relation fixer which is needed lately in determineRelationFixer(..)
    			result = (target, input) -> {
    				// fixing target on source side
    				oneToOneRelation.getReverseSetter().set(input, target);
    				// fixing source on target side
    				sourceIntoTargetFixer.set(target, input);
    			};
    		} else {
    			// non bidirectional relation : relation is owned by target without defining any way to fix it in memory
    			// we can only fix target on source side
    			result = sourceIntoTargetFixer::set;
    		}

    		return result;
    	}

    	private class OneToOneOwnedBySourceHelper<SRC, TRGT, SRCID, TRGTID, LEFTTABLE extends Table<LEFTTABLE>, RIGHTTABLE extends Table<RIGHTTABLE>> {

    		protected ForeignKey<LEFTTABLE, RIGHTTABLE, TRGTID> determineForeignKeyColumns(OneToOneRelation<SRC, TRGT, ?> oneToOneRelation,
    		                                                                               LEFTTABLE leftTable,
    		                                                                               PrimaryKey<RIGHTTABLE, TRGTID> rightPrimaryKey,
    		                                                                               JoinColumnNamingStrategy joinColumnNamingStrategy,
    		                                                                               ForeignKeyNamingStrategy foreignKeyNamingStrategy
    		) {
    			// adding foreign key constraint
    			KeyBuilder<LEFTTABLE, TRGTID> leftKeyBuilder = Key.from(leftTable);
    			AccessorDefinition accessorDefinition = AccessorDefinition.giveDefinition(oneToOneRelation.getTargetProvider());
    			rightPrimaryKey.getColumns().forEach(column -> {
    				String effectiveLeftColumnName = nullable(oneToOneRelation.getColumnName()).elseSet(() -> joinColumnNamingStrategy.giveName(accessorDefinition, column)).get();
    				Column<LEFTTABLE, ?> foreignKeyColumn = leftTable.addColumn(effectiveLeftColumnName, column.getJavaType());
    				leftKeyBuilder.addColumn(foreignKeyColumn);
    			});
    			Key<LEFTTABLE, TRGTID> leftKey = leftKeyBuilder.build();

    			// According to the nullable option, we specify the ddl schema option
    			leftKey.getColumns().forEach(c -> ((Column) c).nullable(oneToOneRelation.isNullable()));

    			String foreignKeyName = foreignKeyNamingStrategy.giveName(leftKey, rightPrimaryKey);
    			return leftTable.addForeignKey(foreignKeyName, leftKey, rightPrimaryKey);
    		}

    		public void addUniqueConstraint(ForeignKey<LEFTTABLE, RIGHTTABLE, TRGTID> foreignKey, UniqueConstraintNamingStrategy uniqueConstraintNamingStrategy, ReadWritePropertyAccessPoint<SRC, TRGT> targetProvider) {
    			Column<LEFTTABLE, ?> column = first(foreignKey.getColumns());
    			String constraintName = uniqueConstraintNamingStrategy.giveName(targetProvider, column);
    			column.getTable().addUniqueConstraint(constraintName, column);
    		}
    	}

    	private class OneToOneOwnedByTargetHelper<SRC, TRGT, SRCID, TRGTID, LEFTTABLE extends Table<LEFTTABLE>, RIGHTTABLE extends Table<RIGHTTABLE>> {

    		protected ForeignKey<RIGHTTABLE, LEFTTABLE, SRCID> determineForeignKeyColumns(OneToOneRelation<SRC, TRGT, ?> oneToOneRelation,
    		                                                                              PrimaryKey<LEFTTABLE, SRCID> leftPrimaryKey,
    		                                                                              RIGHTTABLE rightTable,
    		                                                                              JoinColumnNamingStrategy joinColumnNamingStrategy,
    		                                                                              ForeignKeyNamingStrategy foreignKeyNamingStrategy) {

    			Column<RIGHTTABLE, SRCID> reverseColumn = oneToOneRelation.getReverseColumn();
    			// small to check for incongruous reverse column definition: it can't be possible when key is composite
    			if (reverseColumn != null && leftPrimaryKey.isComposed()) {
    				throw new UnsupportedOperationException("Can't map composite primary key " + leftPrimaryKey + " on single reverse foreign key : " + reverseColumn);
    			}

    			// priority 1: take user definition of reverse column
    			KeyBuilder<RIGHTTABLE, SRCID> rightKeyBuilder = Key.from(rightTable);
    			if (reverseColumn == null) {
    				String reverseColumnName = oneToOneRelation.getReverseColumnName();
    				if (reverseColumnName != null) {
    					Column<LEFTTABLE, SRCID> leftPKColumn = (Column<LEFTTABLE, SRCID>) first(leftPrimaryKey.getColumns());
    					reverseColumn = rightTable.addColumn(reverseColumnName, leftPKColumn.getJavaType());
    					rightKeyBuilder.addColumn(reverseColumn);
    				}
    			} else {
    				rightKeyBuilder.addColumn(reverseColumn);
    			}

    			// priority 2: user didn't define reverse column, but we can guess it from the reverse accessor
    			if (reverseColumn == null) {
    				AccessorDefinition accessorDefinition = AccessorDefinition.giveDefinition(oneToOneRelation.getReverseAccessor().getAccessor());
    				leftPrimaryKey.getColumns().forEach(pkColumn -> {
    					String effectiveLeftColumnName = joinColumnNamingStrategy.giveName(accessorDefinition, pkColumn);
    					Column<RIGHTTABLE, ?> column = rightTable.addColumn(effectiveLeftColumnName, pkColumn.getJavaType());
    					rightKeyBuilder.addColumn(column);
    				});
    			}

    			// According to the nullable option, we specify the ddl schema option
    			Key<RIGHTTABLE, SRCID> rightKey = rightKeyBuilder.build();
    			if (oneToOneRelation.isNullable()) {
    				rightKey.getColumns().forEach(c -> ((Column) c).nullable(true));
    			}

    			String foreignKeyName = foreignKeyNamingStrategy.giveName(rightKey, leftPrimaryKey);
    			return rightTable.addForeignKey(foreignKeyName, rightKey, leftPrimaryKey);
    		}

    		public void addUniqueConstraint(ForeignKey<RIGHTTABLE, LEFTTABLE, SRCID> foreignKey, UniqueConstraintNamingStrategy uniqueConstraintNamingStrategy, ReadWritePropertyAccessPoint<TRGT, SRC> mappedByAccessor) {
    			Column<RIGHTTABLE, ?> column = first(foreignKey.getColumns());
    			String constraintName = uniqueConstraintNamingStrategy.giveName(mappedByAccessor, column);
    			column.getTable().addUniqueConstraint(constraintName, column);
    		}
    	}
    }